package pl.edu.pw.elka.dzielins.revanalyzer.instance;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.math3.exception.NullArgumentException;
import org.sat4j.minisat.orders.SubsetVarOrder;

import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4compiler.ast.Attr;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;

import pl.edu.pw.elka.dzielins.revanalyzer.model.ModelObject;
import pl.edu.pw.elka.dzielins.revanalyzer.model.ModelRelation;

public class InstanceObject {
	private String name;
	private ModelObject baseObject;
	private Map<ModelRelation, Set<InstanceRelation>> relations;
	private Sig sig;

	public InstanceObject(String name, ModelObject baseObject) {
		if(baseObject==null)
			throw new NullArgumentException();
		this.baseObject = baseObject;
		this.relations = new HashMap<ModelRelation, Set<InstanceRelation>>();
		this.name = name;
	}

	// getters & setters

	public String getName() {
		if (name != null)
			return name;
		else
			return baseObject.getName() + String.valueOf(this.hashCode());
	}

	public void setName(String name) {
		this.name = name;
	}

	public ModelObject getBaseObject() {
		return baseObject;
	}

	public Map<ModelRelation, Set<InstanceRelation>> getRelations() {
		return relations;
	}

	public void setRelations(Map<ModelRelation, Set<InstanceRelation>> relations) {
		this.relations = relations;
	}

	// artificial getters & setters

	public void addRelation(InstanceRelation relation) {
		if (!relations.containsKey(relation.getBaseRelation()))
			relations.put(relation.getBaseRelation(), new HashSet<InstanceRelation>());
		Set<InstanceRelation> set = relations.get(relation.getBaseRelation());
		set.add(relation);
	}
	
	public void removeRelation(InstanceRelation relation) {
		if (relation.getOwner() != this)
			return;
		Set<InstanceRelation> set = relations.get(relation.getBaseRelation());
		set.remove(relation);
	}

	public Set<ModelRelation> getPossibleRelations() {
		return baseObject.getAllRelations();
	}
	
	public Set<ModelRelation> getNotUsedRelations() {
		Set<ModelRelation> possibleRelations = getPossibleRelations();
		Set<ModelRelation> notUsedRelations = new HashSet<ModelRelation>();
		for(ModelRelation mr:possibleRelations) {
			if (!relations.containsKey(mr)) {
				notUsedRelations.add(mr);
			} else {
				if(relations.get(mr)==null || relations.get(mr).isEmpty()) {
					notUsedRelations.add(mr);
				}
			}
		}
		return notUsedRelations;
	}
	
	public ModelRelation getPossibleRelation(String relationName) {
		for (ModelRelation modelRelation : baseObject.getAllRelations()) {
			if (modelRelation.getName().equals(relationName))
				return modelRelation;
		}
		return null;
	}
	
	public Sig getSig() throws Err {
		sig = new PrimSig(name, (PrimSig) baseObject.getSig(), Attr.ONE);
		return sig;
	}

	// other methods
	
	public boolean isRelatedWithObject(InstanceObject object) {
		if (this == object)
			return true;
		for (ModelRelation relationKey : relations.keySet())
			for(InstanceRelation relation : relations.get(relationKey)) {
				if (relation.referencesObject(object))
					return true;
			}
		return false;
	}
	
	public Set<InstanceRelation> getRelationsWithObject(InstanceObject object) {
		Set<InstanceRelation> result = new HashSet<InstanceRelation>();
		for (ModelRelation relationKey : relations.keySet())
			for(InstanceRelation relation : relations.get(relationKey)) {
				if (relation.referencesObject(object))
					result.add(relation);
			}
		return result;
	}

	@Override
	public String toString() {
		return getName();
		/*String s;
		s = getName();
		s += " extends " + getBaseObject().getName();
		if (relations == null || relations.isEmpty())
			s += " { ";
		else {
			s+=" {\n";
			for(String key : relations.keySet())
				for(InstanceRelation rel:relations.get(key))
					s+="\t"+rel.toString()+"\n";
		}
		s+="}";
		return s;*/
	}
}
